package com.pvdnc.dxhook;

import android.content.Context;
import android.util.Log;

import com.google.dexmaker.Code;
import com.google.dexmaker.DexMaker;
import com.google.dexmaker.Local;
import com.google.dexmaker.MethodId;
import com.google.dexmaker.TypeId;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class MakerUtils {
    private static final String TAG=MakerUtils.class.getSimpleName();

    public static final TypeId<?> OBJ_ARRAY_TYPE=TypeId.get(Object[].class);

    public static void addDefaultConstructor(DexMaker dexMaker, TypeId<?> declaringType) {
        Code code = dexMaker.declare(declaringType.getConstructor(), Modifier.PUBLIC);
        Local thisRef = code.getThis(declaringType);
        code.invokeDirect(TypeId.OBJECT.getConstructor(), null, thisRef);
        //code.invokeSuper(superType.getConstructor(),null,thisRef);
        code.returnVoid();
    }

    public static MethodId<?,?> getMethodId(TypeId<?> typeId, Method method) throws Exception {
        return getMethodId(typeId,
                method.getName(),
                method);
    }

    public static MethodId<?,?> getMethodId(TypeId<?> typeId,String newMethodName, Method method) throws Exception {
        Class<?>[] paramClasses= method.getParameterTypes();
        List<TypeId<?>> paramTypeList=new ArrayList<>();
        for (Class<?> paramClass:paramClasses){
            paramTypeList.add(TypeId.get(paramClass));
        }
        TypeId[] paramTypes=new TypeId[paramTypeList.size()];
        paramTypeList.toArray(paramTypes);
        Log.d(TAG,"method:"+method.getName()+"\n\t"
                + Arrays.toString(paramTypes));
        Method getMethod=typeId.getClass().getMethod("getMethod", TypeId.class, String.class, TypeId[].class);
        getMethod.setAccessible(true);
        //准备参数
        Class<?> retClass= method.getReturnType();
        TypeId retType=TypeId.get(retClass);
        return (MethodId<?, ?>) getMethod.invoke(typeId,
                retType,newMethodName,paramTypes);
    }

    public static Local<Object[]> buildParamArray(MethodId<?,?> methodId,Code code){
        //获取参数
        List<TypeId<?>> paramTypeList= methodId.getParameters();
        int paramCount=paramTypeList.size();

        Local<Integer> argLength=code.newLocal(TypeId.INT);
        Local<Integer> argIndex=code.newLocal(TypeId.INT);
        Local<Object[]> args=code.newLocal(TypeId.get(Object[].class));
        Local arg_ = code.newLocal(TypeId.OBJECT);
        //变量定义完成
        //申请数组
        code.loadConstant(argLength,paramCount);
        code.newArray(args,argLength);
        //循环赋值
        for (int i = 0; i < paramCount; i++) {
            code.loadConstant(argIndex,i);
            TypeId<?> paramType=paramTypeList.get(i);
            Log.d(TAG,"arg["+i+"] type:"+paramType);
            Local param=code.getParameter(i,paramType);
            //code.aput(args,argIndex,param);
            MethodId mId = ValueTypes.getValueFromType(paramType);
            if (mId != null) {//值类型，要自己拆箱
                code.invokeStatic(mId, arg_, param);
                code.aput(args, argIndex, arg_);
            } else {
                code.aput(args, argIndex, param);
            }
        }
        return args;
    }

    public static Local[] buildParamArrayForReflect(MethodId<?,?> methodId, Code code){
        List<TypeId<?>> paramTypeList= methodId.getParameters();
        int paramCount=paramTypeList.size();
        List<Local> paramList=new ArrayList<>();
        for (int i = 0; i < paramCount; i++) {
            TypeId<?> paramType=paramTypeList.get(i);
            Local paramRef= code.getParameter(i,paramType);
            paramList.add(paramRef);
        }
        Local[] locals=new Local[paramCount];
        paramList.toArray(locals);
        return locals;
    }

    public static void createCallSuper(Code code,Local thisRef,
                                       MethodId<?,?> superMethod,Local retValue,Local... params){
        TypeId<?> retType=superMethod.getReturnType();
        if(TypeId.VOID.equals(retType)){
            code.invokeSuper(superMethod,null,thisRef,params);
            code.returnVoid();
        }else{
            code.invokeSuper(superMethod,retValue,thisRef,params);
            code.returnValue(retValue);
        }
    }

    public static void createCallSuperByReflect(Code code,Local thisRef,
                                       MethodId<?,?> superMethod,Local retValue,Local[] params) throws Exception {
        Method callSuperMethod=code.getClass().getMethod("invokeSuper",
                MethodId.class, Local.class, Local.class, Local[].class);
        callSuperMethod.setAccessible(true);
        TypeId<?> retType=superMethod.getReturnType();
        if(TypeId.VOID.equals(retType)){
            //code.invokeSuper(superMethod,null,thisRef,params);
            callSuperMethod.invoke(code,
                    superMethod,null,thisRef,params);
            code.returnVoid();
        }else{
            //code.invokeSuper(superMethod,retValue,thisRef,params);
            callSuperMethod.invoke(code,
                    superMethod,retValue,thisRef,params);
            code.returnValue(retValue);
        }
    }
}
